// 如果要在 video 上使用，可参考逻辑自行实现：
// 用 src 替换 aid, eid
// 用 tt.preloadResourceVideo 替换 tt.preloadVideo

import type { DramaEpisode } from '@/api/drama';
import type { SwiperOnChangeEvent } from 'mxp-uni-types';
import { type ComponentInternalInstance, type Ref, nextTick, ref } from 'vue';

type SwiperOptions = {
  /** 是否可以轮播，即尾部衔接头部，永为 true */
  circular: boolean;
  /** 切换速度 */
  duration: number;
  /** 当前的swiper-item */
  current: number;
};

type Options = {
  /** 是否是<MxpAppVideo>播放组件 */
  isMxpAppVideo: boolean;
  /** 当前页面实例 */
  instance: ComponentInternalInstance | null;
  /** swiper切换速度 */
  swiperDuration?: number;
  /** 开始集数，从 0 开始 */
  start: number;
  /** 总集数，从 1 开始 */
  total: number;

  /** 滑动到列表末尾执行，例如弹框：最后一集 */
  onEndList?: () => void;
  /** swiperOptions 变化时触发，需要 更新 到页面中 */
  onSwiperOptionsChange: (data: SwiperOptions) => void;
  /** 切换swiper-item时触发，返回 swiper-item的剧集信息列表，需要设置到页面渲染  */
  onSwiperItemInfoListChange: (data: DramaEpisode[]) => void;
  /** video-player 会同时渲染 3 个，当有 video 销毁时触发 */
  onVideoDestroy?: (data: DramaEpisode) => void;
};

/**
 * 这个类会去处理 swiper 滑动切换 video-player 逻辑
 * video-player 的滑动播放模块
 * 具体逻辑逻辑如下：
 * 1. 向下滑动时，改变下下一个 video 地址预加载下下一集。例如：从 1 滑动到 2 时，去请求 3 的播放链接，然后去渲染 3 ，但不播放。
 * 2. 向上滑动时，改变上上一个 video 地址预加载上上一集。例如：从 3 滑动到 2 时，去请求 1 的播放链接，然后去渲染 1 ，但不播放。
 * 3. 当显示的为最后一个或者第一个时，关闭 swiper 的轮播。
 * 4. 当剧集总数除以 3 余数为 1 时，swiper 滑到倒数第 1 集时，swiper 的长度改为 4 个，添加的第 4 个swiper-item 为最后 1 集。当从倒数第 2 集滑到倒数第 3 集时，删除 swiper 的第 4 个 video 改为 3 个进行轮播。
 * 5. 当剧集总数除以 3 余数为 2 时，swiper 滑到倒数第 1 集时，swiper 的长度改为 2，且删除掉最后一个 video。当从倒数第 1 集滑到倒数第 2 集时，在 swiper 尾部添加倒数第 3 集。
 * 6. 当选集时，需要保证第 1 个视频永远在第一个 swiper-item 中，即初始的 swiper current 是：（当前选择的集数 - 1）除以（3）的余数。
 * 上述逻辑请参考 IDE 模板接入文档，里面有详细的分析。
 */
export class VideoSwiper {
  /** 全部剧集信息 */
  public dramaEpisodeList: Ref<DramaEpisode[]> = ref([]);
  /** 当前集 在 videoList 中的索引,从 0 开始 */
  public currentIndex = 0;
  /** 上一集，在 videoList 中的索引，从 0 开始，可能是 -1 */
  private preIndex = 0;
  /** 下一集，在 videoList 中的索引，从 0 开始，可能是 videoList.length */
  private nextIndex = 0;
  /** 是否正在触发队列改变 */
  private runningTrigger = false;
  /** swiper-item 列表 */
  private swiperItemInfoList: DramaEpisode[] = [];
  /** swiper 配置 */
  private swiperOptions: SwiperOptions = {
    /** 是否可以轮播，即尾部衔接头部，永为 true */
    circular: false,
    /** 切换速度 */
    duration: 500,
    /** 当前的swiper-item */
    current: 0,
  };

  /** 传入的配置 */
  private options: Options = {
    isMxpAppVideo: false,
    instance: null,
    swiperDuration: 500,
    start: 0,
    total: 0,
    onEndList: () => undefined,
    onSwiperOptionsChange: () => undefined,
    onSwiperItemInfoListChange: (_data: (DramaEpisode | null)[]) => undefined,
    onVideoDestroy: () => undefined,
  };

  public constructor(dramaEpisodeList: Ref<DramaEpisode[]>, options: Options) {
    this.dramaEpisodeList = dramaEpisodeList;
    this._checkStart(options.start, options.total);
    Object.assign(this.options, options);
    this._init();
  }

  /** 初始化 */
  private _init() {
    // 初始位置需要是除以 3 的余数，。这样才能满足向上滑动时，第一个视频永远在第一个 swiper-item 中
    const swiperCurrent = this.options.start % 3;
    // 初始化小于等于三个的空数组
    this.swiperItemInfoList = this.dramaEpisodeList.value.slice(swiperCurrent, swiperCurrent + 3);
    // 改变当前集，上一集，下一集 的索引（从 0 开始的索引）
    this._changeCurrentIndex(this.options.start);
    const videoInfo = this.dramaEpisodeList.value[this.currentIndex];
    this._setSwiperOptions({ ...this.swiperOptions, current: swiperCurrent, circular: this._checkCanCircular() });

    this._initPreload(swiperCurrent);
    this._swiperItemInfoList(swiperCurrent, videoInfo!);
    this._triggerCurQueueChange();
  }
  /** 触发当前队列改变 */
  private _triggerCurQueueChange() {
    if (!this.runningTrigger) {
      this.runningTrigger = true;
      this.options.onSwiperItemInfoListChange(this.swiperItemInfoList);
      this.runningTrigger = false;
    }
  }

  /** 触发Swiper配置改变 */
  private _triggerSwiperOptionsChange() {
    this.options.onSwiperOptionsChange(this.swiperOptions);
  }

  /** 设置Swiper配置 */
  private _setSwiperOptions(obj: SwiperOptions) {
    Object.assign(this.swiperOptions, obj);

    this._triggerSwiperOptionsChange();
  }

  /** 设置SwiperItem数据 */
  private _swiperItemInfoList(swiperIndex: number, info: DramaEpisode) {
    const { swiperItemInfoList } = this;
    const lastVideoInfo = swiperItemInfoList[swiperIndex];

    if (lastVideoInfo && info && '_id' in lastVideoInfo && '_id' in info && lastVideoInfo._id !== info._id) {
      this.options.onVideoDestroy && this.options.onVideoDestroy(lastVideoInfo);
    }

    swiperItemInfoList[swiperIndex] = info;
    Object.assign(this.swiperItemInfoList, swiperItemInfoList);
  }

  /** 删除SwiperItem数据 */
  private _deleteCurQueue(index: number) {
    const { swiperItemInfoList } = this;
    const videoInfo = swiperItemInfoList[index];

    if (videoInfo && 'episodeSeq' in videoInfo) {
      this.options.onVideoDestroy && this.options.onVideoDestroy(videoInfo);
    }
    swiperItemInfoList.splice(index, 1);
    this.swiperItemInfoList = swiperItemInfoList;
    this._triggerCurQueueChange();
  }

  /** 初始化预加载 */
  private _initPreload(current: number) {
    switch (current) {
      case 0:
        this._preload(1, 'down');
        this._preload(1, 'up');
        break;
      case 1:
        this._preload(1, 'down');
        this._preload(1, 'up');
        break;
      case 2:
        this._preload(1, 'down');
        this._preload(1, 'up');
        break;
      default:
        break;
    }
  }

  /** 检查上一个索引是否有效 */
  private _checkPreIndexValid(preIndex: number) {
    return preIndex >= 0;
  }

  /** 检查下一个索引是否有效 */
  private _checkNextIndexValid(nextIndex: number) {
    return nextIndex < this.options.total;
  }

  /** 验证循环 */
  private _checkCanCircular() {
    return this.currentIndex !== 0 && this.currentIndex !== this.options.total - 1;
  }

  /** 检查开始集数配置 */
  private _checkStart(start: number, total: number) {
    if (start + 1 > total || start < 0) {
      throw new Error('options.start 无效');
    }
  }

  // swiperIndex 和集数到转换
  // swiper-item 只有3个，但集数有很多

  private _formatSwiperIndex(index: number) {
    if (index < 0) {
      const x = index % 3;
      return x === 0 ? 0 : 3 + x;
    }
    return index % 3;
  }
  /** 改变当前集，上一集，下一集 的索引（从 0 开始的索引） */
  private _changeCurrentIndex(targetIndex: number) {
    this.currentIndex = targetIndex;
    this.preIndex = targetIndex - 1;
    this.nextIndex = targetIndex + 1;
  }

  /** 预加载上一级或下一集 */
  private _preload(count: number, direction: 'down' | 'up') {
    const { current } = this.swiperOptions;

    // 滑动方向
    if (direction === 'down') {
      for (let index = 1; index <= count; index++) {
        const nextIndex = this.currentIndex + index;
        const nextSwiperIndex = current + index;
        let normalizedNextSwiperIndex = this._formatSwiperIndex(nextSwiperIndex);

        if (this._checkNextIndexValid(nextIndex)) {
          // 预加载的是最后一集，且当前 swiper-index 是最后一个
          // 需将预加载的最后一集加到 swiper 列表末尾
          if (nextIndex === this.options.total - 1 && current === 2) {
            normalizedNextSwiperIndex = 3;
          }
          const videoInfo = this.dramaEpisodeList.value[nextIndex]!;
          this._swiperItemInfoList(normalizedNextSwiperIndex, videoInfo);
        }
      }
    } else if (direction === 'up') {
      for (let index = 1; index <= count; index++) {
        const preIndex = this.currentIndex - index;
        const preSwiperIndex = current - index;
        const normalizePreSwiperIndex = this._formatSwiperIndex(preSwiperIndex);

        if (this._checkPreIndexValid(preIndex)) {
          const videoInfo = this.dramaEpisodeList.value[preIndex]!;
          this._swiperItemInfoList(normalizePreSwiperIndex, videoInfo);
        }
      }
    }
  }

  /** 当前播放视频发生改变 */
  private _changeCurrent(swiperIndex: number, targetIndex: number, direction: 'down' | 'up') {
    // 理论上不会出现
    if (targetIndex < 0 || targetIndex >= this.options.total) {
      return;
    }
    // 暂停上一个视频
    this.pauseAll();
    // 设置当前播放的视频
    this._changeCurrentIndex(targetIndex);

    this._setSwiperOptions({ ...this.swiperOptions, current: swiperIndex, circular: this._checkCanCircular() });

    const { swiperItemInfoList } = this;
    // 最后一集，且当前 swiper-index 是倒数第二个
    // 需要删除最后一个 swiper-item
    if (targetIndex === this.options.total - 1 && swiperIndex === 1) {
      this._deleteCurQueue(2);
    } else if (targetIndex === this.options.total - 3 && swiperItemInfoList.length === 4) {
      // 倒数第三集，且 swiper 列表为 4 项时，需要删除最后一项
      // 需要删除最后一个 swiper-item
      this._deleteCurQueue(3);
    } else {
      this._preload(1, direction);
    }
  }

  /** 选集 */
  public setStart(start: number) {
    this.pauseAll();
    nextTick(() => {
      this._checkStart(start, this.options.total);
      this.options.start = start;
      this._init();
    });
  }

  /** 更新当前剧集 */
  public updateCurrentVideo() {
    const { currentIndex } = this;
    const currentSwiperIndex = this.swiperOptions.current;
    this._swiperItemInfoList(currentSwiperIndex, this.dramaEpisodeList.value[currentIndex]!);
    this._triggerCurQueueChange();
  }

  /** 获取当前播放的 video */
  public getCurrentVideo(): DramaEpisode {
    return this.dramaEpisodeList.value[this.currentIndex]!;
  }

  /** 获取当前 video context */
  public getVideoContext(id?: string): HTMLVideoElement | UniNamespace.VideoContext | undefined {
    const _id: string | undefined = id || this.dramaEpisodeList.value[this.currentIndex]?._id;
    if (_id === undefined) {
      return undefined;
    } else {
      let context: HTMLVideoElement | UniNamespace.VideoContext | undefined = undefined;
      if (this.options.isMxpAppVideo) {
        context = (document.getElementById(`${_id}`) || undefined) as HTMLVideoElement | undefined;
      } else {
        context = uni.createVideoContext(`${_id}`, this.options.instance);
      }
      return context;
    }
  }

  /** 播放当前剧集 */
  public play() {
    this.getVideoContext()?.play();
  }

  /** 暂停当前剧集 */
  public pause() {
    this.getVideoContext()?.pause();
  }

  /** 暂停当前剧集 */
  public pauseAll() {
    this.swiperItemInfoList.forEach((item) => {
      item._id && this.getVideoContext(item._id)?.pause?.();
    });
  }

  /** swiper bindchange 使用 */
  public onchange(e: SwiperOnChangeEvent) {
    const { current } = e.detail;
    const lastIndex = this.swiperOptions.current;
    const diff = current - lastIndex;
    const direction = diff === 1 || diff === -2 ? 'down' : 'up';
    if (lastIndex === current) {
      return;
    }
    let targetIndex = this.currentIndex;
    if (direction === 'down') {
      targetIndex = this.nextIndex;
    } else {
      targetIndex = this.preIndex;
    }
    this._changeCurrent(current, targetIndex, direction);
    this._triggerCurQueueChange();
  }

  /** video-player bindgetsource 使用 */
  public onGetSource(episodeSeq: number) {
    const currentVideo = this.dramaEpisodeList.value[this.currentIndex]!;
    if (currentVideo?.episodeSeq === episodeSeq && currentVideo.state !== 'lock') {
      this.play();
    }
  }

  /** video-player bindended 使用 */
  public onEnded() {
    const { nextIndex } = this;
    const { current } = this.swiperOptions;

    if (this._checkNextIndexValid(nextIndex)) {
      this._changeCurrent(current + 1, nextIndex, 'down');
    } else if (this.options.onEndList) {
      this.options.onEndList();
    }
  }
}
